package kernel;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import memory.Semaphore;
import IOdevices.Keyboard;
import IOdevices.SimulatedDisk;
import IOdevices.VideoDevice;
import adt.*;


public class CPU extends Thread{

	/**
	 * The max count for each process before reset.
	 */
	public static final int RESETCOUNT = 500;
	
	/**
	 * The timer that generates timer_interrupt.
	 */
	private CPUTimer my_timer = new CPUTimer(this);
	
	/**
	 * the proces that's current running.
	 */
	private CPUprocess my_current_process;
	
	/**
	 * ready queue of the processes.
	 */
	private BlockingQueue<CPUprocess> my_ready_queue = new LinkedBlockingQueue<CPUprocess>();
	
	/**
	 * different type of request and interrupt.
	 * @author Chris Ren
	 *
	 */
	public static enum Request{KEYBOARD_REQUEST, TIMER_REQUEST, SYS_RD_REQUEST, SYS_WR_REQUEST, KEYBOARD_SER_DONE, VIDEO_REQUEST, DISK_REQUEST}
	
	/**
	 * current running process's PCB
	 */
	private ProcessControlBlock my_current_pcb = new ProcessControlBlock();
	
	/**
	 * the semaphore.
	 */
	private final Semaphore my_semaphore = new Semaphore(this);
	
	/**
	 * My keyboard device.
	 */
	private final Keyboard my_keyboard = new Keyboard(this);
	
	/**
	 * My disk device.
	 */
	private final SimulatedDisk my_disk = new SimulatedDisk(this);
	
	/**
	 * my video device.
	 */
	private final VideoDevice my_video = new VideoDevice(this);
	
	/**
	 * my idle process.
	 */
	private final CPUprocess my_idle = new IdleProcess(10000);

	
	/**
	 * loop that runs increase the process count. 
	 * if the event count has been met grab the type of the request sent into context switch.
	 * 
	 */
	public void run()
	{		
		displayQueues();
		while(true)
		{
			my_current_pcb = my_current_process.getPCB();
			System.out.print("");
			if((my_semaphore.getOwner() == null) || (!my_current_process.equals(my_semaphore.getOwner())))
			{
				
				my_current_process.proceessoutput();
				if (my_current_pcb.getStep() >= RESETCOUNT)
				{
					my_current_pcb.resetStep();
				}
				if(my_current_pcb.getStep() == my_current_pcb.getEvent())
				{
					my_current_pcb.service_occur();
					contextSwitch(my_current_process.getRequest());
				}
				else
				{
					my_current_pcb.stepIncrease();
				}
			}
			
		}
	}
	
	/**
	 * Start all the devices and setup the running process.
	 */
	public void start()
	{
		my_keyboard.start();
		my_disk.start();
		my_video.start();
		my_current_process = getProcess();
		my_timer.start();
		my_timer.setPriority(MAX_PRIORITY);
		my_semaphore.start();
		my_semaphore.setPriority(MIN_PRIORITY);
		super.start();	
		
	}
	
	/**
	 * handles all the service request and interrupt. 
	 * Depends on differnt type of the request and interrupt, performs differently.
	 * @param the_request the service request or the interrupt.
	 */
	public void contextSwitch(Request the_request) {
		switch(the_request){
			case KEYBOARD_REQUEST:
				System.out.println(my_current_process.getPid() + " generates an KEYBOARD request.");
				
				my_keyboard.addProcess(my_current_process);
				my_current_process = getProcess();
				break;
			case VIDEO_REQUEST:
				System.out.println(my_current_process.getPid() + " generates an VIDEO request.");
				my_video.addProcess(my_current_process);
				my_current_process = getProcess();
				break;
			case DISK_REQUEST:
				System.out.println(my_current_process.getPid() + " generates an DISK request.");
				my_disk.addProcess(my_current_process);
				my_current_process = getProcess();
				break;
			case SYS_RD_REQUEST:
				System.out.println(my_current_process.getPid() + " generates an MEMORY READ request.");
				if(!my_semaphore.sysread(my_current_process))
				{
					my_current_process = getProcess();
				}
				break;
			case SYS_WR_REQUEST:
				System.out.println(my_current_process.getPid() + " generates an MEMORY WRITE request.");
				if(!my_semaphore.syswrite(my_current_process))
				{
					my_current_process = getProcess();
				}
				break;
			case TIMER_REQUEST:
				System.out.println("CPU: Receieve Timer Interrupt");
				my_semaphore.interrupt();
				CPUprocess temp = my_current_process;
				if (!temp.isIdle())
				{
					my_ready_queue.add(temp);
				}
				my_current_process = getProcess();
				if(my_semaphore.getOwner() != null && my_current_process.equals(my_semaphore.getOwner()))
				{
					my_semaphore.run();
				}
				System.out.println("Timer Interrupt finished");
				break;
			case KEYBOARD_SER_DONE:
				System.out.println("CPU: Receieve User Keyboard Interrupt");
				my_semaphore.interrupt();
				CPUprocess temp1 = my_current_process;
				if (!temp1.isIdle())
				{
					my_ready_queue.add(temp1);
				}
				my_current_process = getProcess();
				if(my_semaphore.getOwner() != null && my_current_process.equals(my_semaphore.getOwner()))
				{
					my_semaphore.run();
				}
				System.out.println("Keyboard Interrupt finished");
				break;
			default:
				System.out.println("Something bad happen.");
				
				break;
		}	
		displayQueues();
		
	}
	
	/**
	 * return the current running process. 
	 */
	public CPUprocess getCurrentProcess()
	{
		return my_current_process;
	}

	/**
	 * called by scheduler getting to next process to run.
	 * 
	 * @return the head of the ready queue, if the queue is empty , return the idle process.
	 */	
	private CPUprocess getProcess() {
		if (my_ready_queue.size() == 0)
		{
			return my_idle;
		}
		else
		{
			return my_ready_queue.poll();
		}
	}

	/**
	 * send the parameter process to the end of ready queue if it isn't an idle.
	 * @param the_process the process that will be sent to the ready queue.
	 */
	public void process_ready(CPUprocess the_process)
	{
		if (!the_process.isIdle())
			my_ready_queue.add(the_process);
	}

	/**
	 * display what's the current process. 
	 * and processes in ready queue, keyboard, video, disk queues.
	 * also display the semaphore info.
	 */
	private void displayQueues() 
	{
		System.out.println("################################################");
		System.out.println("My current runing process is : \n" + my_current_process.toString());
		System.out.println("\nReady Queue:");
		for(CPUprocess p : my_ready_queue)
		{
			System.out.print(p.getPid() + ", ");
		}
		my_keyboard.display();
		my_video.display();
		my_disk.display();
		my_semaphore.display();
		System.out.println();
		System.out.println("################################################");
		
	}
}
